<?php
/**
 * Created by PhpStorm.
 * User: cc
 * Date: 2024/1/16
 * Time: 10:14
 */

namespace app\api\controller\v42;

use app\aliyun\controller\AliyunFilterWordV2;
use app\api\controller\v2\Auth;
use app\common\cachemodel\chat\HzChatRecordDetails;
use app\common\cachemodel\zl7\Zl7AiAnswerNumData;
use app\common\cachemodel\zl7\Zl7QuestionAiAnswerRecord;
use app\common\cachemodel\ZlTarotRecords;
use app\common\lib\exception\ApiException;
use app\common\lib\Util;
use app\common\model\Divination;
use GuzzleHttp\Exception\GuzzleException;
use think\Env;
use think\response\Json;
use XznsTools\curl\Curl;

class AiAnswer extends Auth
{
    /**
     * 获取Ai解答内容
     * @return \think\response\Json
     * @throws \Exception
     */
    public function getAiContent()
    {
        $type = input('type/d', 0);
        $id = input('id/d', 0);
        $res = $this->checkParam();
        if (!$res)
        {
            return show(config('code.error'), '请求异常，请稍后再试～');
        }
        $aiContent = (new Zl7QuestionAiAnswerRecord())->getUserAiInfo($type, $this->user['id'], $id)['content'];
        if ($aiContent === null)
        {
            $status = 2;
        } elseif ($aiContent === '')
        {
            $status = 3;
        } else
        {
            $status = 1;
        }
        return show(config('code.success'), 'Success.', [
            'content' => $aiContent ?: '',
            'status' => $status, //1=>有结果了 2=>调用流式获取结果  3=>服务中断（点击重新加载，重新调用流式接口）
        ]);
    }

    /**
     * 获取流式Ai解答内容
     * @return void
     * @throws \Exception
     * @throws GuzzleException
     */
    public function getStreamContent()
    {
        $type = input('type/d', 0);
        $id = input('id/d', 0);
        $res = $this->checkParam();
        if (!$res)
        {
            echo '';
        } else
        {
            $aiContent = (new Zl7QuestionAiAnswerRecord())->getUserAiInfo($type, $this->user['id'], $id);
            if ($aiContent['content'] === null)
            {
                if (Util::checkAppVersion('2.15.0') !== '小')
                {
                    $hzChatRecordDetailsModel = new HzChatRecordDetails();
                    $numData = $hzChatRecordDetailsModel->getChatQueryEndNumRes($this->user['id']);
                    $aiAnswerNum = $numData['res_num'];
                    if ($aiAnswerNum < 1)
                    {
                        echo '没有可用的AI解析次数了～';
                        exit;
                    }
                    // 扣除次数
                    $numData = $hzChatRecordDetailsModel->chatQueryEndNumDec($this->user['id'], $numData, 1);
                    $numType = $numData['use_type'];
                } else
                {
                    $num = (new Zl7AiAnswerNumData())->getUserAiNum($this->user['id']);
                    if ($num['ai_num'] + $num['admin_num'] < 1)
                    {
                        echo '没有可用的AI解析次数了～';
                        exit;
                    }
                    //扣除解答次数
                    if ($num['admin_num'] > 0)
                    {   // 扣除 赠送次数
                        (new Zl7AiAnswerNumData())->updateData($this->user['id'], ['admin_num' => ['dec', 1], 'use_num' => ['inc', 1], 'update_time' => $this->request->time()]);
                        $numType = 4;
                    } else
                    {
                        (new Zl7AiAnswerNumData())->updateData($this->user['id'], ['ai_num' => ['dec', 1], 'use_num' => ['inc', 1], 'update_time' => $this->request->time()]);
                        $numType = 3;
                    }
                }
                (new Zl7QuestionAiAnswerRecord())->saveData($this->user['id'], $type, [
                    'uid' => $this->user['id'],
                    'type' => $type,
                    'did' => $id,
                    'status' => 0,
                    'content' => '',
                    'create_time' => $this->request->time(),
                    'num_type' => $numType,
                ]);
            } elseif ($aiContent['content'] === '')
            {
                $numType = $aiContent['num_type'];
            } else
            {
                echo $aiContent['content'];
                exit;
            }
            if ($type === 1)
            {
                $planet = (new Util())->PlanetTypeName($res['planet']);
                $sign = (new Util())->signTypeName($res['sign'])['name'];
                $house = $res['house'] . '宫';
                $dice_or_tarot_info = [$planet, $sign, $house];
            } else
            {
                $tarotConfig = config('tarot');
                $content = $res['content'];
                $before = $now = $future = '';
                foreach ($content as $item)
                {
                    switch ($item['location'])
                    {
                        case 1://过去
                            $before = $tarotConfig['card_list'][$item['card_id']]['name'] . '.' . ((int)$item['direction'] === 1 ? '正位' : '逆位');
                            break;
                        case 2://现在
                            $now = $tarotConfig['card_list'][$item['card_id']]['name'] . '.' . ((int)$item['direction'] === 1 ? '正位' : '逆位');
                            break;
                        case 3://未来
                            $future = $tarotConfig['card_list'][$item['card_id']]['name'] . '.' . ((int)$item['direction'] === 1 ? '正位' : '逆位');
                            break;
                    }
                }
                $dice_or_tarot_info = [$before, $now, $future];
            }
            $content = $this->pythonStream('/api/get_dice_or_tarot_result', [
                'uid' => $this->user['id'],
                'dice_or_tarot_info' => $dice_or_tarot_info,//当 type=0时，示例 ['北交点', '双子座', '9宫'] 当 type=1时，示例 ['宝剑5.逆位', '星币国王.正位', '圣杯国王.正位'] 索引为0是过去，索引为1是现在，索引为2是未来
                'did' => $id,
                'type' => $type - 1,
                'query' => $res['question'],
                'brand_index' => 0,
            ]);
            $aiId = (new Zl7QuestionAiAnswerRecord())->getUserAiInfo($type, $this->user['id'], $id)['id'];
            $uData = [
                'status' => 1,
                'content' => $content,
                'update_time' => $this->request->time(),
            ];
            if (strpos($content, 'θ') !== false)
            {
                $uData['content'] = '准准认为你的问题包含敏感信息，换个问题再试试吧。本次解答不扣次数！';
                $uData['num_type'] = 0;
                (new HzChatRecordDetails())->chatQueryEndNumInc($this->user['id'], $numType);
            }
            (new Zl7QuestionAiAnswerRecord())->updateData($aiId, $uData);
        }
    }

    /**
     * 检测问题AI是否可以回答
     * @return Json
     * @throws \Exception
     * @throws GuzzleException
     */
    public function checkQuestion()
    {
        $type = input('type/d', 0);
        $id = input('id/d', 0);
        $res = $this->checkParam();
        if (!$res)
        {
            return show(config('code.error'), '请求异常，请稍后再试～');
        }
        $url = Env::get('haozhungpt.url') . '/api/check_query';
        $pythonRes = Curl::request($url, ['query' => $res['question']], 'post', ['Content-Type' => 'application/json'], 'json');
        if ($pythonRes['success'] === false)
        {
            return show(config('code.error'), '请求异常，请稍后再试～');
        }
        if (!$pythonRes['data'])
        {
            if ($type === 1)
            {
                $msg = '你提的问题不适合使用骰子解答，建议你换一个问题。';
            } else
            {
                $msg = '你提的问题不适合使用塔罗解答，建议你换一个问题。';
            }
            return show(config('code.success'), 'Success.', [
                'msg' => $msg,
                'status' => 2,
            ]);
        }
        //当用户在超过24小时的详情页内点击Al解答，提示用户超过时效。
        if ($res['create_time'] < time() - 24 * 3600)
        {
            return show(config('code.success'), 'Success.', [
                'msg' => '建议你使用新的问题和结果进行AI解答，因为问题和结果具有时效性，3个月内的问题和当下的结果更能保证准确性。',
                'status' => 2, //1=>不弹窗   2=弹窗
            ]);
        }
        return show(config('code.success'), 'Success.', ['msg' => '', 'status' => 1]);
    }

    /**
     * 检验请求参数
     * @return array|bool|mixed
     * @throws \Exception
     */
    private function checkParam()
    {
        $type = input('type/d', 0);
        $id = input('id/d', 0);
        if (!in_array($type, [1, 2], true))
        {
            return [];
        }
        if ($type === 1)
        {
            $data = (new Divination())->getDetail(['id' => $id]);
        } else
        {
            $data = (new ZlTarotRecords())->getInfoById($id);
        }
        if (!$data || $data['uid'] != $this->user['id'])
        {
            return [];
        }

        $aliRes = AliyunFilterWordV2::checkAIText($data['question'], 'llm_query_moderation_02');
        if ($aliRes['status'] === false)
        {
            throw new ApiException('检测到敏感内容');
        }
        return $data;
    }

    /**
     * 请求python接口,流式输出
     * @param $url
     * @param $chatParams
     * @return mixed|string|true
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    private function pythonStream($url, $chatParams)
    {
        $url = Env::get('haozhungpt.url') . $url;
        // 这行代码用于关闭输出缓冲。关闭后，脚本的输出将立即发送到浏览器，而不是等待缓冲区填满或脚本执行完毕。
//        ini_set('output_buffering', 'off');

        // 这行代码禁用了 zlib 压缩。通常情况下，启用 zlib 压缩可以减小发送到浏览器的数据量，但对于服务器发送事件来说，实时性更重要，因此需要禁用压缩。
//        ini_set('zlib.output_compression', false);
        ignore_user_abort(true); // 忽略客户端断开连接
        ob_implicit_flush(true); // 开启隐式刷新
        // 这行代码使用循环来清空所有当前激活的输出缓冲区。ob_end_flush() 函数会刷新并关闭最内层的输出缓冲区，@ 符号用于抑制可能出现的错误或警告。
        try
        {
            while (ob_get_level() > 0 && @ob_end_flush())
            {
            }
        }
        catch (\Exception $e)
        {
//            \think\Log::write(__CLASS__ . ' => ' . __FUNCTION__ . '()异常：' . $e->getMessage() . '失败信息：' . $e->getFile() . ':' . $e->getLine(), 'error', true);
        }
        // 这行代码设置 HTTP 响应的 Content-Type 为 text/event-stream，这是服务器发送事件（SSE）的 MIME 类型。
        header('Content-Type: text/event-stream');

        // 这行代码设置 HTTP 响应的 Cache-Control 为 no-cache，告诉浏览器不要缓存此响应。
        header('Cache-Control: no-cache');

        // 这行代码设置 HTTP 响应的 Connection 为 keep-alive，保持长连接，以便服务器可以持续发送事件到客户端。
        header('Connection: keep-alive');

        header('X-Accel-Buffering: no');//这一行必须存在，否则不好使（X-Accel-Buffering 头信息设置为“no”，表示已经禁用缓冲机制，服务器会立即发送响应数据，以确保客户端能够尽早地接收到数据。而如果设置为“yes”或不设置该头信息，则表示启用缓冲机制，服务器会将响应数据写入缓冲区，然后批量发送给客户端。)
        // 创建一个 HTTP Client 实例
//        $client = new Client([
//            'timeout' => 60 // 超时时间60秒
//        ]);
//
//        // 调用 request 方法发送请求
//        $response = $client->request("POST", $url, [
//            'headers' => [
//                'Content-Type' => 'application/json'
//            ],
//            'json' => $chatParams, // 设置请求参数
//            'stream' => true // 设置流式输出
//        ]);
//
//        // 输出响应体
//        $stream = $response->getBody();
//
//        $oldStr = '';
//        $resStr = '';
//        $flag = 0;
//        while (!$stream->eof())
//        {
//            $res = $stream->read(1); // 每次读取 1 字节，可以根据需求进行调整
//            if ($res === "\n")
//            {
//                $flag++;
//            } else
//            {
//                $flag = 0;
//            }
//            $resStr .= $res;
//            if ($flag >= 4)
//            {   // 检测到3个 \n\n\n 则将数据 输出到页面
//                $resStr = rtrim($resStr, "\n\n\n\n");
//                if ($resStr)
//                {
//                    usleep(50000);
//                    echo $resStr; // 输出一波数据
//                    flush(); //刷新缓存（直接发送到浏览器）
//                }
//                $oldStr = $resStr;
//                $resStr = ''; // 置空
//                $flag = 0;
//            }
//        }
        $resData = Curl::requestStream($url, $chatParams, 'POST', ['Content-Type' => 'application/json', 'stream' => true], static function ($stream)
        {
            $resStr = '';
            while (!$stream->eof())
            {
                $res = $stream->read(1); // 每次读取 1 字节，可以根据需求进行调整
                echo $res; // 输出一波数据
                flush(); //刷新缓存（直接发送到浏览器）

                $resStr .= $res;
            }

            return $resStr;
        }, 'json');
        if ($resData['success'])
        {
            return $resData['data'];
        }
        return '';
    }
}
